#pragma once

#include <vector>
#include <string>
#include <iostream>

namespace my
{
	// lint == Effective_big_long_long_integer
	class lint
	{
	protected:

		typedef long long unit;

		enum unit_sign
		{
			positive = '+',
			negative = '-'
		};

		static const signed char unitDigit	= 18;
		static const unit half				= 1000000000;
		static const unit upLimit			= 1000000000000000000;
											//9223372036854775807
										   //18446744073709551615
		std::vector<unit> _nums;
		unit_sign _sign = positive;

	private:

		bool _isJustHead18() const
		{
			if (_nums.size() == 1)
			{
				return true;
			}

			unit Head = _getHead18();
			*((lint*)this) -= 1;

			bool just = !(Head == _getHead18());

			*((lint*)this) += 1;
			return just;
		}

		size_t _getHead18() const
		{
			if (_nums.size() == 1)
			{
				return _nums[0];
			}

			unit HeadNum = _nums[_nums.size() - 1];
			unit temp = _nums[_nums.size() - 2];
			int rest = unitDigit - digitCountUnit(HeadNum);
			size_t setDigit = upLimit / 10;

			while (rest)
			{
				HeadNum = HeadNum * 10 + temp / setDigit % 10;
				setDigit /= 10;
				--rest;
			}
			return HeadNum;
		}

		void _downTen(long long n)
		{
			_nums.erase(_nums.begin(), _nums.begin() + n / unitDigit);
			*this /= _pow(10, n % unitDigit);
		}

		void _upTen(long long n)
		{
			_nums.insert(_nums.begin(), n / unitDigit, 0);
			*this *= _pow(10, n % unitDigit);
		}

		void signChange() const
		{
			int* change = (int*)(&_sign);
			*change = (_sign == positive ? negative : positive);
		}

		unit justThanSize(const lint& number) const;

		static lint _pow(const lint& number, long long index);

		static unit_sign signExpressSolve(const std::string& str);

		static size_t signLastIndex(const std::string& str);

		static unit atoi(const std::string& number, int left, int right)
		{
			unit sum = 0;
			for (int i = left; i < right; ++i)
			{
				sum = sum * 10 + (number[i] - '0');
			}
			return sum;
		}

		static size_t digitCountUnit(unit n)
		{
			size_t count = 0;
			for (size_t temp = 1; temp <= n; temp *= 10)
			{
				++count;
			}
			return count;
		}

	public:

		std::string to_string() const;

		void downTen(long long n)
		{
			if (n <= 0)
			{
				return;
			}
			_downTen(n);
		}

		void upTen(long long n)
		{
			if (n <= 0)
			{
				return;
			}
			_upTen(n);
		}

		size_t digitCount() const
		{
			return digitCountUnit(_nums[_nums.size() - 1]) + (_nums.size() - 1) * unitDigit;
		}

		static lint pow(const lint& number, long long index)
		{
			if (index == 0)
			{
				return 1;
			}
			if (number == 0 || index < 0)
			{
				return 0;
			}

			return _pow(number, index);
		}

	public:

		void swap(lint& number)
		{
			std::swap(_nums, number._nums);
			std::swap(_sign, number._sign);
		}

		lint(lint&& number) = default;
		lint(const lint& number) = default;
		lint& operator=(const lint& number) = default;
		lint& operator=(lint&& number) = default;

		lint(const char* number)
		{
			*this = std::string(number);
		}

		lint(const std::string& number)
		{
			_sign = signExpressSolve(number);
			int downLimit = signLastIndex(number);

			_nums.reserve(number.size() / unitDigit + 1);

			for (int len = number.size(); len > downLimit + 1; len -= unitDigit)
			{
				_nums.push_back(atoi(number, (len - unitDigit >= 0 ? len - unitDigit : 0), len));
			}

			while (_nums.size() > 1 && _nums[_nums.size() - 1] == 0)
			{
				_nums.pop_back();
			}
		}

		lint(const std::vector<unit>&& number)
		{
			_nums = std::move(number);
		}

		lint(unsigned long long number)
			:_sign(positive)
		{
			if (number >= upLimit)
			{
				_nums.push_back(number % upLimit);
				number /= upLimit;
			}
			_nums.push_back(number);
		}
		lint(const long long number = 0)
			:_sign(number < 0 ? negative : positive)
		{
			unit temp = number;
			if (temp < 0)
			{
				temp = -temp;
			}
			unit next = temp / upLimit;
			_nums = { temp % upLimit };
			if (next > 0)
			{
				_nums.push_back(next);
			}
		}
		lint(const unsigned int number)
		{
			*this = lint((long long)number);
		}
		lint(const int number)
		{
			*this = lint((long long)number);
		}
		lint(const unsigned short number)
		{
			*this = lint((long long)number);
		}
		lint(const short number)
		{
			*this = lint((long long)number);
		}
		lint(const unsigned char number)
		{
			*this = lint((long long)number);
		}
		lint(const signed char number)
		{
			*this = lint((long long)number);
		}

	public:

		lint& operator+=(const lint& number);
		lint& operator-=(const lint& number);
		lint& operator*=(const lint& number);
		lint& operator/=(const lint& number);
		lint& operator%=(const lint& number);
		lint& operator<<=(const long long n);
		lint& operator>>=(const long long n);

	public:

		lint operator-() const
		{
			lint temp = *this;
			temp.signChange();
			return temp;
		}
		lint& operator--()
		{
			return *this -= 1;
		}
		lint operator--(int)
		{
			lint temp = *this;
			--(*this);
			return temp;
		}

		lint operator+() const
		{
			return *this;
		}
		lint& operator++()
		{
			return *this += 1;
		}
		lint operator++(int)
		{
			lint temp = *this;
			++(*this);
			return temp;
		}

	public:

		friend bool operator==(const lint& number1, const lint& number2);
		friend bool operator< (const lint& number1, const lint& number2);

		friend std::ostream& operator<<(std::ostream& out, const lint& number);
		friend std::istream& operator>>(std::istream& in, lint& number);
	};

	lint operator+(const lint& number1, const lint& number2);
	lint operator-(const lint& number1, const lint& number2);
	lint operator*(const lint& number1, const lint& number2);
	lint operator/(const lint& number1, const lint& number2);
	lint operator%(const lint& number1, const lint& number2);
	lint operator<<(const lint& number1, const long long n);
	lint operator>>(const lint& number1, const long long n);

	bool operator<=(const lint& number1, const lint& number2);
	bool operator>(const lint& number1, const lint& number2);
	bool operator>=(const lint& number1, const lint& number2);
	bool operator!=(const lint& number1, const lint& number2);
	bool operator!(const lint& number);
}
